import math
from datetime import datetime, timedelta
from functools import reduce
from typing import Sequence, Type, TypeVar, Tuple, List

import numpy as np
import random

from emi.bar.data import Interval, BarData
from emi.util import utils, TimeUtils

T = TypeVar('T')

### 这个时间，创业板涨跌停限制放开到20%
CY_NEW_LIMIT_TIME_STAMP = datetime(year=2020, month=8, day=24).timestamp()


class BarUtils:
    A_DAY = 24 * 60 * 60





    @staticmethod
    def makeBars_minutes(symbol:str, time:datetime, bar_class_type = BarData,adjust_price = 1.0,amount = 12000.5,pre_close = 0):
        """
        生成随机bars
        """
        yy_mm_dd,hh_mm_ss = TimeUtils.split_datetime(time)
        min_bar: BarData = None
        max_bar: BarData = None

        bars_list = []
        for i in range(0,len(BarUtils.TYPE101_HH_MM_LIST)):
            hh_mm_ss = BarUtils.TYPE101_HH_MM_LIST[i] * 100
            interval = Interval.MINUTE
            dt = TimeUtils.merge_datetime(yy_mm_dd,hh_mm_ss)
            bar = bar_class_type(datetime=dt, interval=interval, symbol=symbol)
            if max_bar is None or max_bar.datetime < bar.datetime:
                max_bar = bar
            if min_bar is None or min_bar.datetime > bar.datetime:
                min_bar = bar
            bar.volume = int(random.random()*100000000)
            bar.high_price = float(int(random.random()*1000000 + random.random()) / 100)
            bar.close_price = float(int(random.random()*1000000 + random.random()) / 100)
            bar.low_price = float(int(random.random()*1000000 + random.random()) / 100)
            bar.open_price = float(int(random.random()*1000000 + random.random()) / 100)
            bar.price_adjust = adjust_price
            bar.amount = amount
            bar.pre_close = pre_close
            bars_list.append(bar)
        return bars_list, min_bar, max_bar

    @staticmethod
    def makeBars_symbols(symbols: [], intervals: [], set_time: datetime = None):
        """
        生成随机bars
        """
        assert not set_time is None
        interval_size = len(intervals)
        min_bar: BarData = None
        max_bar: BarData = None
        time_seed = datetime.now() - timedelta(days=300)
        time_detal = 0
        bars_list = []
        for i in range(0,len(symbols)):
            symbol = symbols[i]
            interval = intervals[random.randint(0, interval_size - 1)]
            time_detal += random.randint(1, 4)
            dt = set_time
            if dt is None:
                dt = time_seed + timedelta(days=time_detal)
            bar = BarData(datetime=dt, interval=interval, symbol=symbol)
            if max_bar is None or max_bar.datetime < bar.datetime:
                max_bar = bar
            if min_bar is None or min_bar.datetime > bar.datetime:
                min_bar = bar
            bar.volume = float(int(random.random()*1000000 + random.random()) / 100)
            bar.high_price = float(int(random.random()*1000000 + random.random()) / 100)
            bar.close_price = float(int(random.random()*1000000 + random.random()) / 100)
            bar.low_price = float(int(random.random()*1000000 + random.random()) / 100)
            bar.open_price = float(int(random.random()*1000000 + random.random()) / 100)
            bars_list.append(bar)
        return bars_list, min_bar, max_bar

    @staticmethod
    def makeBars(n:int, symbols:[], intervals:[], set_time:datetime = None, bar_type:T = BarData)->Tuple[List[T], T, T]:
        """
        生成随机bars
        """
        assert n > 0
        symbol_size = len(symbols)
        interval_size = len(intervals)
        time_seed = datetime.now() - timedelta(days=300)
        min_bar:T = None
        max_bar:T = None

        time_detal = 0
        bars_list = []
        while len(bars_list) < n:
            symbol =symbols[random.randint(0,symbol_size-1)]
            interval =intervals[random.randint(0,interval_size-1)]
            time_detal+= random.randint(1,4)
            dt = set_time
            if dt is None:
                dt = time_seed + timedelta(days=time_detal)
            bar = bar_type(datetime=dt,interval = interval,symbol = symbol)
            if max_bar is None or max_bar.datetime < bar.datetime:
                max_bar = bar
            if min_bar is None or min_bar.datetime > bar.datetime:
                min_bar = bar
            bar.volume = float(random.random()*1000000 + random.random())
            bar.high_price = float(random.random()*1000000 + random.random())
            bar.close_price = float(random.random()*1000000 + random.random())
            bar.low_price = float(random.random()*1000000 + random.random())
            bar.open_price = float(random.random()*1000000 + random.random())
            bars_list.append(bar)
        return bars_list,min_bar,max_bar

    """
    返回np.narray形式：low,high,open,close,volume
    """
    def to_np_array(bars:['BarData']):
        size = len(bars)
        low = np.full(size,0.0)
        high = np.full(size,0.0)
        open = np.full(size,0.0)
        close = np.full(size,0.0)
        volume = np.full(size,0.0)
        for i in range(size):
            low[i] = bars[i].low_price
            high[i] = bars[i].high_price
            open[i] = bars[i].open_price
            close[i] = bars[i].close_price
            volume[i] = bars[i].volume
        return low,high,open,close,volume

    @staticmethod
    def reduce_bars_by_same_day(bars:['BarData']):
        """
        将分钟bar行情reduce到同一天，并升序排序。
        return
            [ [day1的分钟行情bars],[day2的分钟行情bars]]
        """
        size = len(bars)

        yy_mm_dd, hh_MM_SS = TimeUtils.split_datetime(bars[0].datetime)
        a_day_bars = []
        a_day_bars.append(bars[0])
        bars_list = []
        for i in range(1,size):
            bar = bars[i]
            the_day,the_hh_mm_ss = TimeUtils.split_datetime(bar.datetime)
            if the_day == yy_mm_dd:
                ##同一天
                assert the_hh_mm_ss > hh_MM_SS  ## 保证顺序
                a_day_bars.append(bar)
            else:
                ##不在同一天
                assert the_day > yy_mm_dd  ## 保证顺序
                bars_list.append(a_day_bars)
                yy_mm_dd = the_day
                hh_MM_SS = the_hh_mm_ss
                a_day_bars = []
                a_day_bars.append(bar)
        if len(a_day_bars) > 0:
            bars_list.append(a_day_bars)
        return bars_list

    """
    将零散的整理bars的价格
    """
    def arrangePrice(bars:['BarData'], basePrice:float, accumulate=True)->['BarData']:

        barList = []
        new_open_price = basePrice
        for bar in bars:
            open_price = bar.open_price
            close_price = bar.close_price
            high_price = bar.high_price
            low_price = bar.low_price
            close_price = close_price * new_open_price / open_price
            high_price = high_price * new_open_price / open_price
            low_price = low_price * new_open_price / open_price
            open_price = new_open_price

            barList.append( BarData(
                symbol=bar.symbol,
                datetime=bar.datetime,
                interval=Interval.WEEKLY,
                volume=bar.volume,
                open_price=open_price,
                high_price=high_price,
                low_price=low_price,
                close_price=close_price,
            ))
            if accumulate:
                new_open_price = close_price
        return barList

    @staticmethod
    def isAllOpen(bars:['BarData']) ->bool:
        for bar in bars:
            if not BarUtils.isOpen(bar):
                return False
        return True

    @staticmethod
    def isOpen(bar:BarData) ->bool:
        if bar is None:
            return False
        return bar.volume > 0.9

    @staticmethod
    def get_v_price(bar: BarData,one_hand:float = 1,check = True) -> float:
        if NumUtils.is_equal(bar.high_price, bar.low_price) or bar.volume < 100 or bar.amount < 100:
            ## 成交量太小，就没必要计算
            return (bar.high_price+bar.low_price) / 2
        vprice = bar.amount / (bar.volume * one_hand) * bar.price_adjust
        if not check:
            return vprice
        if vprice > bar.high_price or vprice < bar.low_price:
            if bar.interval == Interval.M5 or bar.interval == Interval.MINUTE or bar.interval.TICK:
                ## 由于是分钟级别数据，允许一定的误差
                pnl_diff = (vprice - bar.high_price) / bar.high_price if vprice > bar.high_price else (
                                                                                                                  bar.low_price - vprice) / bar.low_price
                if abs(pnl_diff) > 0.1:
                    ##误差过大
                    raise RuntimeError(f"vprice 不准确: vprice:{vprice},diff={100 * pnl_diff:.1f}%, bar:{bar}")
            else:
                raise RuntimeError(f"vprice 不准确: vprice:{vprice}, bar:{bar}")
        return vprice

    """
    返回最大的间隔天数
    """
    @staticmethod
    def getMaxIntervalDay(bars:Sequence['BarData'])->int:
        _len = len(bars)
        if _len < 2:
            return 0
        max_day = 1
        for i in range(1,_len):
            _day = (bars[i].datetime - bars[i-1].datetime).days
            assert _day > 0
            if _day > max_day:
                max_day = _day
        return max_day

    def getMaxSellBuyPct(bars:Sequence['BarData'], basePrice):
        sell_pct = 100 * ((bars[0].high_price + bars[0].close_price) / 2 - basePrice) / basePrice
        buy_pct = 100 * ((bars[0].low_price + bars[0].close_price) / 2 - basePrice) / basePrice
        for i in range(1,len(bars)):
            bar = bars[i]
            _s_pct = 100 * ((bar.high_price + bar.close_price) / 2 - basePrice) / basePrice
            _b_pct = 100 * ((bar.low_price + bar.close_price) / 2 - basePrice) / basePrice
            sell_pct = max(_s_pct, sell_pct)
            buy_pct = min(_b_pct, buy_pct)
        return  sell_pct,buy_pct

    def getAvgClosePct(bars:Sequence['BarData'], basePrice):
        close_price = 0
        size = len(bars)
        for i in range(0,size):
            bar = bars[i]
            close_price += bar.close_price
        return  100 * (close_price / size - basePrice) / basePrice

    """
    在各个天的涨幅情况。
    """
    def getPctList(bars:Sequence['BarData'], basePrice)->np.ndarray:
        size = len(bars)
        pct_list = np.full(len(bars),size)
        for i in range(0,size):
            bar = bars[i]
            pct_list[i] = 100 * (bar.close_price - basePrice) / basePrice
        return  pct_list

    @staticmethod
    def filterNoOpen(bars):
        new_bars = []
        for bar in bars:
            if BarUtils.isOpen(bar):
                new_bars.append(bar)
        return new_bars

    @staticmethod
    def is_in_tarde_time(time:datetime) -> float:
        if time.hour < 9:
            return False
        if time.hour == 9 and time.minute < 30:
            return False
        if time.hour == 11 and time.minute >30:
            return False
        if time.hour > 11 and time.hour < 13:
            return False
        if time.hour == 15 and time.minute != 0:
            return False
        if time.hour >15:
            return False
        return True

    @staticmethod
    def get_bar_avg_price(bar:BarData)->float:
        return (bar.open_price + bar.close_price + bar.high_price + bar.low_price) / 4

    @staticmethod
    def stdConvertMintueBarVolume(minute_bars:Sequence['BarData'], is_grand_volume=False):
        """
        检查分钟级别的vol是不是符合规范。
        这里的规范是指，当时刻的分钟级别bar的vol代表的当前时刻的vol成交量，而不是当天时刻从开盘到现在所有累加的成交量，如果是这种情况，需要转换成符合标准的方式。
        """
        pre_volumn = 0
        cur_total_vol = 0
        bar_size = len(minute_bars)
        assert bar_size > 0
        check_grand_rank = True  ##检查是否按升序排序
        total_vol = 0
        for i in range(0, bar_size):
            bar: BarData = minute_bars[i]
            total_vol+=bar.volume
            if is_grand_volume:
                ##分钟级别的成交量是否是累加形式
                ##改成非累加方式
                _minute_volume = bar.volume - cur_total_vol
                print(f"date:{bar.datetime}, {bar.volume}-{cur_total_vol} = {_minute_volume}")
                if _minute_volume < 0:
                    if(i == bar_size -1 ):
                        _minute_volume = 0
                    else:
                        # for __bar in minute_bars:
                        #     print(f"date:{__bar.datetime}: vol = {__bar.volume}")
                        raise RuntimeError(f"分钟级别的bar的vol异常，后一时刻的vol应该大于等于前一时刻的. \n time1 = {minute_bars[i-1].datetime},vol1={cur_total_vol},time2={bar.datetime},vol2={bar.volume}")
                cur_total_vol = bar.volume
                bar.volume = _minute_volume
            else:
                if check_grand_rank:
                    if bar.volume < pre_volumn:
                        check_grand_rank = False
                pre_volumn = bar.volume
        if total_vol < 1:
            print(f" ===> 检测到{minute_bars[0].symbol}在{minute_bars[0].datetime.strftime('%Y-%m-%d')}没有成交量，废弃该天数据！")
            return []
        if not is_grand_volume and check_grand_rank:
            print("检测到分钟bar的vol是按升序排序的，检测下is_grand_volume是否传对！！！")
        return minute_bars



    @staticmethod
    def __compute_vibrate__(values:[]):
        v_size = len(values)
        if v_size < 2:
            return 0
        delta_value = 0.0   ## 总共差值
        for i in range(1,v_size):
            _the_delta =  float(abs(values[i] - values[i-1]))
            delta_value += _the_delta * _the_delta
        return math.sqrt(delta_value / (v_size - 1))


    @classmethod
    def equals(cls, b1:BarData, b2:BarData, P = 0.00011):

        if not b1.symbol == b2.symbol:
            print("BarData.symobl not equals")
            return False
        if  abs(b1.high_price -b2.high_price) > P:
            print(f"BarData.high_price not equals : {b1.high_price} , {b2.high_price}")
            return False
        if  abs(b1.low_price - b2.low_price) > P:
            print(f"BarData.low_price not equals:{b1.low_price} , {b2.low_price}")
            return False
        if  abs(b1.close_price - b2.close_price) > P:
            print(f"BarData.close_price not equals: {b1.close_price} , {b2.close_price}")
            return False
        if  abs(b1.open_price - b2.open_price) > P:
            print(f"BarData.open_price not equals : {b1.open_price} , {b2.open_price}")
            return False
        if  abs(b1.volume - b2.volume) > P:
            print("BarData.volume not equals")
            return False
        if not b1.interval == b2.interval:
            print("BarData.interval not equals")
            return False
        if not (b1.datetime.year == b2.datetime.year
                and b1.datetime.month == b2.datetime.month
                and b1.datetime.day == b2.datetime.day
                and b1.datetime.hour == b2.datetime.hour
                and b1.datetime.minute == b2.datetime.minute
                ##由于压缩存储原因，对于秒就没有那么精准，应该忽略秒
                ##and b1.datetime.second == b2.datetime.second
        ):
            return False

        return b1.extra == b2.extra

    @staticmethod
    def is_limit_pnl(symbol: str, pnl: float, time: datetime) -> int:
        """
        是否达到价格限制： 1：涨停，0：不是，-1：跌停
        """
        if symbol.startswith("3") and time.timestamp() > CY_NEW_LIMIT_TIME_STAMP:
            if pnl > 19.3:
                return 1
            if pnl < - 19.3:
                return -1
            return 0
        if pnl > 9.3:
            return 1
        if pnl < -9.3:
            return -1
        return 0

    @staticmethod
    def is_limit_bar(bar:BarData,pnl = None) -> int:
        """
        是否达到价格限制： 1：涨停，0：不是，-1：跌停
        """
        if pnl is None:
            pnl = bar.pnl
        return BarUtils.is_limit_pnl(symbol=bar.symbol,pnl=pnl,time=bar.datetime)

# +   ls_rate_v1: 相对于当天avg_price的多空力量对比，值在[-1, 1]
# 之间；
#
# long_power: 看多力量， 价格走势高于avg_price的[成交量 * (价格 - avg_price)]
# 总和
# short_power: 看空力量， 价格走势高于avg_price的[成交量 * (avg_price - 价格)]
# 总和
# ls_rateV1 = (long_power - short_power) / (long_power + short_power)
#
# +   ls_rate_v2: 相对于上一天的avg_price的多空力量对比，值在[-1, 1]
# 之间；
# +   price_vibrate_v1: 相对于avg_price价格振动：  计算当天每个分钟时刻相对于avg_price的涨幅price_mintiue_rate, price_vibrate = 方差(
#     price_mintiue_rate)
# +   price_vibrate_v2: 相对于上一天avg_price价格振动：  计算当天每个分钟时刻相对于avg_price的涨幅price_mintiue_rate, price_vibrate = 方差(
#     price_mintiue_rate)
#
# +   super_long_power: 超看多力量， 价格走势高于sell_price的量价值 ：  (价格 - sell_price) * 成交量
# +   super_short_power: 超看空力量， 价格走势低于buy_price的量价值 ：  （buy_price - 价格） *成交量
# +   watch_power: 观望理性力量 ：  abs(价格 - avg_price) * 成交量